﻿//////////////////////////////////////////////////////////////////////////////
//
// Copyright © 1998-2024 Glodon Company Limited.
//
// Licensed under the MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the “Software”),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//////////////////////////////////////////////////////////////////////////////
#include "ItemViewValidateHelper.h"
#include "BalloonTipManager.h"
#include "ValidatorBase.h"
#include "GbmpNew.h"

#include <QPainter>
#include <QLineEdit>
#include <QKeyEvent>
#include <QTableView>
#include <QTreeView>
#include <QHeaderView>

#include "EnableCompileWarning_The_LAST_IncludeInCpp.h"

namespace gcmp
{

ItemViewValidateHelper::ViewEventFilter::ViewEventFilter(ItemViewValidateHelper *pDelegate)
    : QObject(pDelegate)
    , m_pDelegate(pDelegate)
{
}

bool ItemViewValidateHelper::ViewEventFilter::eventFilter(QObject* pWatched, QEvent* pEvent)
{
    if (!m_pDelegate || !m_pDelegate->m_pView || (m_pDelegate->m_pView != pWatched))
    {
        return false;
    }

    const QEvent::Type type = pEvent->type();
    if (type == QEvent::KeyPress)
    {
        QKeyEvent* pKeyEvent = static_cast<QKeyEvent*>(pEvent);
        const int key = pKeyEvent->key();
        if ((key == Qt::Key_Up) ||
            (key == Qt::Key_Down) ||
            (key == Qt::Key_Left) ||
            (key == Qt::Key_Right))
        {
            if (!m_pDelegate->m_editingValueIsValid)
                return true;
        }
    }

    return QObject::eventFilter(pWatched, pEvent);
}

ItemViewValidateHelper::ItemViewValidateHelper(const QColor& color, QAbstractItemView *pParent)
    : QStyledItemDelegate(pParent)
    , m_color(color)
    , m_pView(pParent)
    , m_editingValueIsValid(true)
    , m_editingIndex(QModelIndex())
{
    if (m_pView)
    {
        // createEditor里会设置m_viewSelectionMode, m_viewFocusPolicy, m_viewHorizontableHeaderClickable, m_viewVerticalHeaderClickable

        ViewEventFilter* pFilter = NEW_AS_QT_CHILD(ViewEventFilter, this);
        m_pView->installEventFilter(pFilter);
    }
}

QWidget* ItemViewValidateHelper::createEditor(QWidget* pParent, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
    Q_UNUSED(option);

    if (m_editingIndex.isValid())
        return nullptr;

    m_editingIndex = index;

    QLineEdit* pLineEdit = NEW_AS_QT_CHILD(QLineEdit, pParent);
    pLineEdit->setObjectName("StyleNeedObjectName");
    connect(pLineEdit, &QLineEdit::textEdited, this, &ItemViewValidateHelper::OnTextEdited);
    connect(pLineEdit, &QLineEdit::editingFinished, this, &ItemViewValidateHelper::OnEditingFinished);

    if (m_pView)
    {
        m_viewSelectionMode = m_pView->selectionMode();
        m_viewFocusPolicy = m_pView->focusPolicy();

        // 编辑时需要禁止选择
        m_pView->setSelectionMode(QAbstractItemView::NoSelection);

        // 处理输入非法时，鼠标点击其他格子，再按Tab会切换currentIndex的问题
        m_pView->setFocusPolicy(Qt::NoFocus);

        // 输入时禁止点Header
        DisableHeaderClickable();
    }

    return pLineEdit;
}

void ItemViewValidateHelper::destroyEditor(QWidget* pEditor, const QModelIndex& index) const
{
    pEditor->show();
    pEditor->setFocus();

    QLineEdit *pLineEdit = static_cast<QLineEdit*>(pEditor);
    bool valid = Validate(pLineEdit);
    if (!valid)
        return;

    QAbstractItemDelegate::destroyEditor(pEditor, index);
    m_editingIndex = QModelIndex();

    if (m_pView)
    {
        m_pView->setSelectionMode(static_cast<QAbstractItemView::SelectionMode>(m_viewSelectionMode));
        m_pView->setFocusPolicy(static_cast<Qt::FocusPolicy>(m_viewFocusPolicy));
        RestoreHeaderClickable();
    }
}

void ItemViewValidateHelper::setEditorData(QWidget* pEditor, const QModelIndex& index) const
{
    QString text = index.model()->data(index, Qt::EditRole).toString();
    QLineEdit* pLineEdit = static_cast<QLineEdit*>(pEditor);
    pLineEdit->setText(text);
    pLineEdit->textEdited(text);
}

bool ItemViewValidateHelper::editorEvent(QEvent* pEvent, QAbstractItemModel* pModel, const QStyleOptionViewItem& option, const QModelIndex& index)
{
    if (!m_editingValueIsValid && m_pView)
    {
        const QEvent::Type type = pEvent->type();
        if (type == QEvent::MouseButtonPress)
        {
            QSignalBlocker blocker(m_pView->selectionModel());
            m_pView->setCurrentIndex(m_editingIndex);
            return true;
        }
    }
    return QStyledItemDelegate::editorEvent(pEvent, pModel, option, index);
}

// 处理输入非法时，按Tab等的情况
bool ItemViewValidateHelper::eventFilter(QObject* pWatched, QEvent* pEvent)
{
    const QEvent::Type type = pEvent->type();
    if (type == QEvent::KeyPress)
    {
        QKeyEvent* pKeyEvent = static_cast<QKeyEvent*>(pEvent);
        if ((pKeyEvent->key() == Qt::Key_Tab) || (pKeyEvent->key() == Qt::Key_Backtab))
        {
            if (!m_editingValueIsValid)
                return true;
        }
    }

    return QStyledItemDelegate::eventFilter(pWatched, pEvent);
}

QAbstractItemView* ItemViewValidateHelper::GetParentItemView() const
{
    return m_pView;
}

void ItemViewValidateHelper::SetValidatorsForColumns(int columnIdRole, const std::unordered_map<std::wstring, const ValidatorBase*>& validators)
{
    m_validatorIdRole = columnIdRole;
    m_columnValidators.clear();
    for (auto i : validators)
    {
        if (i.second != nullptr)
            m_columnValidators[i.first] = i.second;
    }
}

const ValidatorBase* ItemViewValidateHelper::GetValidatorForColumn(int column) const
{
    const ValidatorBase* pValidator = nullptr;
    if (!m_pView)
        return pValidator;

    // 从列获取
    std::wstring validatorId = m_pView->model()->headerData(column, Qt::Horizontal, m_validatorIdRole).toString().toStdWString();
    auto searchColumn = m_columnValidators.find(validatorId);
    if (searchColumn != m_columnValidators.cend())
    {
        pValidator = searchColumn->second;
    }

    return pValidator;
}

void ItemViewValidateHelper::SetValidatorsForCell(const QMap<QModelIndex, const ValidatorBase*>& validators)
{
    m_cellValidators.clear();
    for (auto i = validators.cbegin(); i != validators.cend(); ++i)
    {
        if (i.key().isValid() && (i.value() != nullptr))
        {
            m_cellValidators.insert(i.key(), i.value());
        }
    }
}

const ValidatorBase* ItemViewValidateHelper::GetValidatorForCell(const QModelIndex& cell) const
{
    const ValidatorBase* pValidator = nullptr;
    if (!m_pView || !m_pView->model())
    {
        return pValidator;
    }

    // 从格子获取
    auto searchGrid = m_cellValidators.find(cell);
    if (searchGrid != m_cellValidators.cend())
    {
        pValidator = searchGrid.value();
    }

    return pValidator;
}

void ItemViewValidateHelper::OnTextEdited()
{
    QLineEdit *pEdit = qobject_cast<QLineEdit*>(sender());
    Validate(pEdit, false);
}

void ItemViewValidateHelper::OnEditingFinished()
{
    QLineEdit *pEdit = qobject_cast<QLineEdit*>(sender());
    Validate(pEdit, true);
}

const ValidatorBase* ItemViewValidateHelper::GetCurrentValidator() const
{
    const ValidatorBase* pValidator = nullptr;
    if (!m_pView || !m_pView->model())
    {
        return pValidator;
    }

    // 从列获取
    int column = m_editingIndex.column();
    std::wstring validatorId = m_pView->model()->headerData(column, Qt::Horizontal, m_validatorIdRole).toString().toStdWString();
    auto searchColumn = m_columnValidators.find(validatorId);
    if (searchColumn != m_columnValidators.cend())
    {
        pValidator = searchColumn->second;
    }

    // 从格子获取
    if (!pValidator)
    {
        auto searchGrid = m_cellValidators.find(m_editingIndex);
        if (searchGrid != m_cellValidators.cend())
        {
            pValidator = searchGrid.value();
        }
    }

    return pValidator;
}

void ItemViewValidateHelper::Validate(QLineEdit *pEdit, bool editingFinished)
{
    if (!pEdit)
        return;

    bool valid = Validate(pEdit);
    if (!valid)
    {
        m_editingValueIsValid = false;
        const ValidatorBase* pValidator = GetCurrentValidator();
        std::wstring tip = pValidator ? pValidator->GetTip({ m_editingIndex.row() }) : L"";
        BalloonTipManager::GetInstance().ShowBalloon(pEdit, tip);
    }
    else
    {
        m_editingValueIsValid = true;
        BalloonTipManager::GetInstance().Clear();

        if (editingFinished && m_pView)
        {
            m_pView->model()->setData(m_editingIndex, pEdit->text());
            destroyEditor(pEdit, m_editingIndex);
        }
    }

    emit validStateUpdated(m_editingIndex, valid);
}

bool ItemViewValidateHelper::Validate(const QLineEdit* pEdit) const
{
    bool valid = true;
    if (!pEdit)
        return valid;

    const ValidatorBase* pValidator = GetCurrentValidator();
    if (pValidator)
    {
        std::wstring text = pEdit->text().toStdWString();
        valid = pValidator->ValidateInput(text, { m_editingIndex.row() });
    }
    return valid;
}

void ItemViewValidateHelper::DisableHeaderClickable() const
{
    // Qt只有QTableView和QTreeView可以有QHeaderView
    QTableView* pTableView = qobject_cast<QTableView*>(m_pView);
    if (pTableView)
    {
        QHeaderView* pHeader = pTableView->horizontalHeader();
        if (pHeader)
        {
            m_viewHorizontableHeaderClickable = pHeader->sectionsClickable();
            pHeader->setSectionsClickable(false);
        }

        pHeader = pTableView->verticalHeader();
        if (pHeader)
        {
            m_viewVerticalHeaderClickable = pHeader->sectionsClickable();
            pHeader->setSectionsClickable(false);
        }
    }

    QTreeView* pTreeView = qobject_cast<QTreeView*>(m_pView);
    if (pTreeView)
    {
        QHeaderView* pHeader = pTreeView->header();
        if (pHeader)
        {
            m_viewHorizontableHeaderClickable = pHeader->sectionsClickable();
            m_viewVerticalHeaderClickable = false;
            pHeader->setSectionsClickable(false);
        }
    }
}

void ItemViewValidateHelper::RestoreHeaderClickable() const
{
    QTableView* pTableView = qobject_cast<QTableView*>(m_pView);
    if (pTableView)
    {
        QHeaderView* pHeader = pTableView->horizontalHeader();
        if (pHeader)
            pHeader->setSectionsClickable(m_viewHorizontableHeaderClickable);

        pHeader = pTableView->verticalHeader();
        if (pHeader)
            pHeader->setSectionsClickable(m_viewVerticalHeaderClickable);
    }

    QTreeView* pTreeView = qobject_cast<QTreeView*>(m_pView);
    if (pTreeView)
    {
        QHeaderView* pHeader = pTreeView->header();
        if (pHeader)
            pHeader->setSectionsClickable(m_viewHorizontableHeaderClickable);
    }
}

}   // end namespace gcmp
